/**
 * 
 */
package org.seginfo.httpfuzzer.connection;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.seginfo.httpfuzzer.FuzzCase;
import org.seginfo.httpfuzzer.connection.status.Status;
import org.seginfo.httpfuzzer.connection.status.StatusFactory;
import org.seginfo.httpfuzzer.header.HeaderProperty;
import org.seginfo.httpfuzzer.header.Host;

import sun.security.krb5.internal.i;

/**
 * @author mchiodi
 *
 */
public class FuzzHttpRequest extends HttpURLConnection {
	
	public static final String VERSION_1_0 = "1.0";
	public static final String VERSION_1_1 = "1.1";
	
	public static final String HEADER_HOST = "Host";
	public static final String HEADER_USER_AGENT = "User-Agent";
	public static final String HEADER_CONNECTION = "Connection";
	public static final String HEADER_CONTENT_LENGTH = "Content-Length";
	public static final String HEADER_SERVER = "Server";
	
	public static final String NEW_LINE = "\r\n";
	
	private FuzzCase fuzzCase;
	private String path;
	private Socket socket;
	private OutputStream outputStream;
	private InputStream inputStream;
	
	private String headerSeparator;
	
	/* Response */
	private Map responseProperties;
	private String server;
	
	/**
	 * @param url
	 */
	public FuzzHttpRequest(URL host, FuzzCase fuzzCase) {
		super(host);
		this.fuzzCase = fuzzCase;
		this.path = fuzzCase.getPath();
		this.headerSeparator = "\r\n";
	}

	/**
	 * @see java.net.HttpURLConnection#usingProxy()
	 */
	public boolean usingProxy() {
		return false;
	}

	/**
	 * @see java.net.URLConnection#connect()
	 */
	public void connect() throws IOException {
		
		this.outputStream = this.getSocket().getOutputStream();
		this.inputStream = this.getSocket().getInputStream();
		
		/* Write the HTTP request */
		//System.out.println("Requesting...");
		this.writeRequest();
		
		/* Read response */
		//System.out.println("Reading...");
		this.readResponse();
		
		/* Close connection */
		this.socket.close();
	}
	
	/**
	 * @throws IOException
	 */
	private void writeRequest() throws IOException {
		
		Writer writer = new OutputStreamWriter(this.outputStream);
		
		/* Write method line */
		String method = this.fuzzCase.getMethod();
		String path = this.path;
		String methodLine = method + ' ' + path + " HTTP/" +
			fuzzCase.getHttpVersion() + NEW_LINE;
		writer.write(methodLine);
		//System.out.print(methodLine);
		
		//writer.write(HEADER_HOST + ": " + this.url.getHost() + NEW_LINE);
		//writer.write(HEADER_USER_AGENT + ": IE8.0" + NEW_LINE);
		//writer.write(HEADER_CONNECTION + ": close" + NEW_LINE);
		//System.out.print(HEADER_HOST + ": " + this.url.getHost() + NEW_LINE);
		//System.out.print(HEADER_USER_AGENT + ": IE8.0" + NEW_LINE);
		//System.out.print(HEADER_CONNECTION + ": close" + NEW_LINE);
		this.fuzzCase.getRequestProperties().add(new Host(this.url.getHost()));
		
		/* Write header fields */
		Iterator iterator = this.fuzzCase.getRequestProperties().iterator(); //this.getRequestProperties().keySet().iterator();
		while (iterator.hasNext()) {
			HeaderProperty property = (HeaderProperty) iterator.next();
			//String fieldValue = (String) this.fuzzCase.getRequestProperties().get(fieldKey); //this.getRequestProperties().get(fieldKey);
			writer.write(property.getName() + ": " + property.getValue() + NEW_LINE);
			//System.out.println(property.getName() + ": " + property.getValue());
		}
		
		/* Write content length header fields */
		int requestContentLength = this.getRequestContentLength();
		if (this.fuzzCase.useAlternativeContentLength()) {
			writer.write(HEADER_CONTENT_LENGTH + ": ");
			writer.write(this.fuzzCase.getAlternativeContentLength());
			writer.write(NEW_LINE);
			
			//System.out.print(HEADER_CONTENT_LENGTH + ": ");
			//System.out.print(this.fuzzCase.getAlternativeContentLength());
			//System.out.print(NEW_LINE);
		} else {
			if (requestContentLength > 0) {
				writer.write(HEADER_CONTENT_LENGTH + ": " + new Long(requestContentLength).toString() + NEW_LINE);
				//System.out.println(HEADER_CONTENT_LENGTH + ": " + new Long(requestContentLength).toString() + NEW_LINE);
			}
		}
		
		/* Write header separator */
		writer.write(this.headerSeparator);
		//System.out.println("");
		
		/* Write content */
		if (requestContentLength > 0) {
			//System.out.println("Real content length: " + this.fuzzCase.getContent().length);
			char[] content = this.fuzzCase.getContent();
			for (int i = 0; i < content.length; i++) {
				writer.write(content[i]);
				System.out.print(content[i]);
			}
		}
		
		writer.flush();
		//System.out.println("");
	}
	
	/**
	 * @return
	 */
	private int getRequestContentLength() {
		if (this.fuzzCase.getContent() != null) {
			return this.fuzzCase.getContent().length;
		} else {
			return 0;
		}
	}
	
	/**
	 * @return
	 */
	private InputStream getRequestContentInputStream() throws IOException {
		if (this.fuzzCase.getContent() != null) {
			return new ByteArrayInputStream(new String(this.fuzzCase.getContent()).getBytes());
		} else {
			return null;
		}
	}
	
	/**
	 * @throws IOException
	 */
	private void readResponse() throws IOException {
		
//		int av = this.inputStream.available();
//		while (av == 0) {
//			this.outputStream.flush();
//			av = this.inputStream.available();
//		}
		Reader reader = new InputStreamReader(this.inputStream);
		
		/* Status line */
		String statusLine = "";
		int times = 5;
		do {
			statusLine = this.readLine(reader);
			if (statusLine.equals("")) {
				synchronized (Thread.currentThread()) {
					try {
						Thread.currentThread().wait(500);
					} catch (InterruptedException e) {
					}
				}
			}
			times--;
		} while (statusLine.equals("") && times > 0);
		super.responseCode = -1;
		if (statusLine.length() > 13) {
			try {
				super.responseCode = Integer.parseInt(statusLine.substring(9,12));
			} catch (NumberFormatException e) {
				throw new RuntimeException(e);
			}
		} else {
			throw new RuntimeException();
		}
		
		/* Read header fields */
		int contentLength = 0;
		this.responseProperties = new HashMap();
		String fieldLine = null;
		do {
			fieldLine = this.readLine(reader);
			int colonIndex = fieldLine.indexOf(':');
			if (colonIndex > -1) {
				String fieldKey = fieldLine.substring(0, colonIndex);
				String fieldValue = fieldLine.substring(colonIndex + 1);
				this.responseProperties.put(fieldKey, fieldValue);
				if (fieldKey.equals(HEADER_CONTENT_LENGTH)) {
					try {
						contentLength = Integer.parseInt(fieldValue.trim());
					} catch (NumberFormatException e) {
					}
				} else if (fieldKey.equals(HEADER_SERVER)) {
					this.server = fieldValue.trim();
				}
			}
		} while (!NEW_LINE.equals(fieldLine));
		
		/* Read content */
//		StringBuffer content = new StringBuffer();
//		long readLength = 0;
//		while (readLength < contentLength) {
//			content.append((char) reader.read());
//			readLength++;
//		}
	}
	
	/**
	 * @see java.net.HttpURLConnection#getResponseCode()
	 */
	public int getResponseCode() {
		return super.responseCode;
	}
	
	/**
	 * @return
	 */
	public Status getStatus() {
		return StatusFactory.getStatus(this.getResponseCode());
	}
	
	/**
	 * @return
	 */
	public String getServer() {
		return this.server;
	}

	/**
	 * @param reader
	 * @return
	 */
	private String readLine(Reader reader) throws IOException {
		StringBuffer line = new StringBuffer();
		int readChar = -1;
		while (reader.ready() && readChar != '\n') {
			readChar = reader.read();
			line.append((char) readChar);
		}
		return line.toString();
	}
	
	/**
	 * @return
	 * @throws IOException
	 */
	private Socket getSocket() throws IOException {
		
		if (this.socket == null) {
			
			/* Get port */
			int port = this.url.getPort();
			if (port == -1) {
				port = 80;
			}
			
			/* Create socket */
			this.socket = new Socket(this.url.getHost(),port);
		}
		
		return this.socket;
	}

	/**
	 * @see java.net.HttpURLConnection#disconnect()
	 */
	public void disconnect() {
		try {
			this.socket.close();
		} catch (IOException e) {
		}
	}
}
